package filerw

import (
	"errors"
	"io"
	"os"
	"sync"
)

// 数据
type data []byte

// DataFile 表示数据文件接口
type DataFile interface {
	// 读操作，读取一个数据块
	// @return: rsn 读数据的时候数据块编号, d 数据体， err 错误信息
	Read() (rsn int64, d data, err error)

	// 写操纵，写入一个数据块
	// @param: d 数据体
	// @return: wsn 写数据的时候数据块编号， err 错误信息
	Write(d data) (wsn int64, err error)

	// 获取最后读取的数据块编号
	RSN() int64

	// 获取最后写入的数据库编号
	WSN() int64

	// 获取数据库长度
	DataLen() uint32

	// 关闭文件
	Close() error
}

// myDataFile 表示数据文件的实现类型
type myDataFile struct {
	f       *os.File     // 文件
	fmutex  sync.RWMutex // 用于文件的读写锁
	woffset int64        // 写操作需要用到的偏移量
	roffset int64        // 读操作需要用到的偏移量

	// 保证 woffset 同时有多个写操作来增加偏移量的时候，确保互斥性
	wmutex sync.Mutex // 写操作需要用到的互斥锁

	// 保证 roffset 同时有多个读操作来增加偏移量的时候，确保互斥性
	rmutex sync.Mutex // 读操作需要用到的互斥锁

	dataLen uint32 // 数据块长度
}

// NewDataFile 创建 DataFile
// @param: path 路径， dataLen 数据块长度
// @return: DataFile 数据文件， error 错误信息
func NewDataFile(path string, dataLen uint32) (DataFile, error) {
	file, err := os.Create(path)
	if err != nil {
		return nil, err
	}
	if dataLen == 0 {
		return nil, errors.New("数据块大小为0！")
	}
	df := &myDataFile{f: file, dataLen: dataLen}
	return df, nil
}

// 这是第一个版本
// 使用 DataFile 中的 fmutex 读写锁，适当进行读加锁 和 读解锁，可以保证读取到是完整的数据库
// 但是这个数据块却并不一定是正确的！
// 因为当有 3 个goroutine 并发地执行某个 *myDataFile 类型的 Read() 函数，并且有两个 goroutine 并发
// 地执行该值的write() 函数，但是由于进行写操作的 goroutine 比读取的 goroutine 少，因此过不了多久，
// 读取偏移量就会等于甚至大于偏移量 woffset 的值，就是很可能出现没有数据可以读！
// 通俗的讲：读取操作，可以并发读，而写操作一次只能有一个 goroutine 进行写，也就是说读取的速度可能超过了写的速度！

// 读操作，读取一个数据块
// @return: rsn 读数据的时候数据块编号, d 数据体， err 错误信息
func (df *myDataFile) Read() (rsn int64, d data, err error) {

	// 读取并更新读取偏移量
	var offset int64
	df.rmutex.Lock()
	// 获取偏移量，也就是我本次要进行读取操作的偏移量
	offset = df.roffset
	// 更新偏移量，也即是下次使用
	df.roffset += int64(df.dataLen)

	df.rmutex.Unlock()

	// 读取一个数据块
	rsn = offset / int64(df.dataLen)

	// 获取读锁
	df.fmutex.RLock()
	defer df.fmutex.RUnlock()

	bytes := make([]byte, df.dataLen)
	// 读取 offset 的数据量到 bytes 里面
	_, err = df.f.ReadAt(bytes, offset)
	if err != nil {
		return 0, nil, err
	}
	d = bytes

	return rsn, d, nil
}

// 第二个版本，解决第一个版本的存在的问题
// 针对第一个版本留下的问题，做出改进
// 使用 for 循环达到一个目的，在其中的 df.f.ReadAt(...) 函数返回了 io.EOF错误的时候，
// 继续尝试读取同一块数据，知道读取到为止！因为 io.EOF 错误可以当做是一个数据的边界！在这个边界继续重复读取
// for循环内，出现任何错误，都要释放读锁，防止其他 goroutine 进行写操作的时候，获取不到写锁

// 读操作，读取一个数据块
// @return: rsn 读数据的时候数据块编号, d 数据体， err 错误信息
func (df *myDataFile) Read2() (rsn int64, d data, err error) {

	// 读取并更新读取偏移量
	var offset int64
	df.rmutex.Lock()
	// 获取偏移量，也就是我本次要进行读取操作的偏移量
	offset = df.roffset
	// 更新偏移量，也即是下次使用
	df.roffset += int64(df.dataLen)

	df.rmutex.Unlock()

	// 读取一个数据块
	rsn = offset / int64(df.dataLen)

	bytes := make([]byte, df.dataLen)

	for {
		// 获取读锁
		df.fmutex.RLock()
		// 读取 offset 的数据量到 bytes 里面
		_, err = df.f.ReadAt(bytes, offset)
		if err != nil {
			if err == io.EOF {
				// 防止死锁
				// 如果读取的时候，是 io.EOF 错误，就把读锁释放
				// 防止其他 goroutine 执行写操作的时候，获取不到写锁
				// 释放读锁
				df.fmutex.RUnlock()
				continue
			}

			// 如果是其他的错误，比如 中途文件被删了，需要把错误信息放回给外面，进行处理
			// 释放读锁
			df.fmutex.RUnlock()
			return 0, nil, err
		}
		d = bytes
		// 释放读锁
		df.fmutex.RUnlock()
		return rsn, d, nil
	}
}

// 写操纵，写入一个数据块
// @param: d 数据体
// @return: wsn 写数据的时候数据块编号， err 错误信息
func (df *myDataFile) Write(d data) (wsn int64, err error) {

	// 读取并更新写偏移量
	var offset int64
	df.wmutex.Lock()
	// 获取偏移量, 也就是我本次的需要进行写入的偏移量
	offset = df.woffset
	// 更新偏移量
	df.woffset += int64(df.dataLen)
	df.wmutex.Unlock()

	// 写入数据到
	wsn = offset / int64(df.dataLen)
	var bytes []byte
	// 如果需要写入的数据，大于 df.dataLen 数据库长度
	// 需要进行截取，不然会出现数据对应不上去
	if len(d) > int(df.dataLen) {
		bytes = d[0:df.dataLen]
	} else {
		bytes = d
	}

	// 开始写入
	df.fmutex.Lock()
	defer df.fmutex.Unlock()
	_, err = df.f.Write(bytes)
	return wsn, nil
}

// 获取最后读取的数据块编号
func (df *myDataFile) RSN() int64 {
	df.rmutex.Lock()
	defer df.rmutex.Unlock()
	return df.roffset / int64(df.dataLen)
}

// 获取最后写入的数据库编号
func (df *myDataFile) WSN() int64 {
	df.wmutex.Lock()
	defer df.wmutex.Unlock()
	return df.woffset / int64(df.dataLen)
}

// 获取数据块长度
func (df *myDataFile) DataLen() uint32 {
	return df.dataLen
}

// 关闭文件
func (df *myDataFile) Close() error {
	return df.f.Close()
}
